//
// Created by boil on 2023/2/17.
//

#ifndef RENDU_CORE_SIGNAL_DELEGATE_H_
#define RENDU_CORE_SIGNAL_DELEGATE_H_

#include "base/type_traits.h"
#include "fwd.h"

namespace rendu {

/**
 * @cond TURN_OFF_DOXYGEN
 * Internal details not to be documented.
 */

namespace internal {

template<typename Ret, typename... Args>
constexpr auto function_pointer(Ret (*)(Args...)) -> Ret (*)(Args...);

template<typename Ret, typename Type, typename... Args, typename Other>
constexpr auto function_pointer(Ret (*)(Type, Args...), Other &&) -> Ret (*)(Args...);

template<typename Class, typename Ret, typename... Args, typename... Other>
constexpr auto function_pointer(Ret (Class::*)(Args...), Other &&...) -> Ret (*)(Args...);

template<typename Class, typename Ret, typename... Args, typename... Other>
constexpr auto function_pointer(Ret (Class::*)(Args...) const, Other &&...) -> Ret (*)(Args...);

template<typename Class, typename Type, typename... Other>
constexpr auto function_pointer(Type Class::*, Other &&...) -> Type (*)();

template<typename... Type>
using function_pointer_t = decltype(function_pointer(std::declval<Type>()...));

template<typename... Class, typename Ret, typename... Args>
[[nodiscard]] constexpr auto index_sequence_for(Ret (*)(Args...)) {
  return std::index_sequence_for<Class..., Args...>{};
}

} // namespace internal

/**
 * Internal details not to be documented.
 * @endcond
 */

/**
 * @brief Basic delegate implementation.
 *
 * Primary template isn't defined on purpose. All the specializations give a
 * compile-time error unless the template parameter is a function type.
 */
template<typename>
class delegate;

/**
 * @brief Utility class to use to send around functions and members.
 *
 * Unmanaged delegate for function pointers and members. Users of this class are
 * in charge of disconnecting instances before deleting them.
 *
 * A delegate can be used as a general purpose invoker without memory overhead
 * for free functions possibly with payloads and bound or unbound members.
 *
 * @tparam Ret Return type of a function type.
 * @tparam Args Types of arguments of a function type.
 */
template<typename Ret, typename... Args>
class delegate<Ret(Args...)> {
  template<auto Candidate, std::size_t... Index>
  [[nodiscard]] auto wrap(std::index_sequence<Index...>) noexcept {
    return [](const void *, Args... args) -> Ret {
      [[maybe_unused]] const auto arguments = std::forward_as_tuple(std::forward<Args>(args)...);
      return static_cast<Ret>(std::invoke(Candidate,
                                          std::forward<type_list_element_t < Index, type_list < Args...>>>(std::get<
        Index>(arguments))...));
    };
  }

  template<auto Candidate, typename Type, std::size_t... Index>
  [[nodiscard]] auto wrap(Type &, std::index_sequence<Index...>) noexcept {
    return [](const void *payload, Args... args) -> Ret {
      [[maybe_unused]] const auto arguments = std::forward_as_tuple(std::forward<Args>(args)...);
      Type *curr = static_cast<Type *>(const_cast<constness_as_t<void, Type> *>(payload));
      return static_cast<Ret>(std::invoke(Candidate,
                                          *curr,
                                          std::forward<type_list_element_t < Index, type_list < Args...>>>(std::get<
        Index>(arguments))...));
    };
  }

  template<auto Candidate, typename Type, std::size_t... Index>
  [[nodiscard]] auto wrap(Type *, std::index_sequence<Index...>) noexcept {
    return [](const void *payload, Args... args) -> Ret {
      [[maybe_unused]] const auto arguments = std::forward_as_tuple(std::forward<Args>(args)...);
      Type *curr = static_cast<Type *>(const_cast<constness_as_t<void, Type> *>(payload));
      return static_cast<Ret>(std::invoke(Candidate,
                                          curr,
                                          std::forward<type_list_element_t < Index, type_list < Args...>>>(std::get<
        Index>(arguments))...));
    };
  }

 public:
  /*! @brief Function type of the contained target. */
  using function_type = Ret(const void *, Args...);
  /*! @brief Function type of the delegate. */
  using type = Ret(Args...);
  /*! @brief Return type of the delegate. */
  using result_type = Ret;

  /*! @brief Default constructor. */
  delegate() noexcept
      : instance{nullptr},
        fn{nullptr} {}

  /**
   * @brief Constructs a delegate with a given object or payload, if any.
   * @tparam Candidate Function or member to connect to the delegate.
   * @tparam Type Type of class or type of payload, if any.
   * @param value_or_instance Optional valid object that fits the purpose.
   */
  template<auto Candidate, typename... Type>
  delegate(connect_arg_t<Candidate>, Type &&...value_or_instance) noexcept {
    connect<Candidate>(std::forward<Type>(value_or_instance)...);
  }

  /**
   * @brief Constructs a delegate and connects an user defined function with
   * optional payload.
   * @param function Function to connect to the delegate.
   * @param payload User defined arbitrary data.
   */
  delegate(function_type *function, const void *payload = nullptr) noexcept {
    connect(function, payload);
  }

  /**
   * @brief Connects a free function or an unbound member to a delegate.
   * @tparam Candidate Function or member to connect to the delegate.
   */
  template<auto Candidate>
  void connect() noexcept {
    instance = nullptr;

    if constexpr (std::is_invocable_r_v<Ret, decltype(Candidate), Args...>) {
      fn = [](const void *, Args... args) -> Ret {
        return Ret(std::invoke(Candidate, std::forward<Args>(args)...));
      };
    } else if constexpr (std::is_member_pointer_v<decltype(Candidate)>) {
      fn = wrap<Candidate>(internal::index_sequence_for<type_list_element_t < 0,
                                                        type_list
                                                            < Args...>>>(internal::function_pointer_t<decltype(Candidate)>{}));
    } else {
      fn = wrap<Candidate>(internal::index_sequence_for(internal::function_pointer_t<decltype(Candidate)>{}));
    }
  }

  /**
   * @brief Connects a free function with payload or a bound member to a
   * delegate.
   *
   * The delegate isn't responsible for the connected object or the payload.
   * Users must always guarantee that the lifetime of the instance overcomes
   * the one of the delegate.<br/>
   * When used to connect a free function with payload, its signature must be
   * such that the instance is the first argument before the ones used to
   * define the delegate itself.
   *
   * @tparam Candidate Function or member to connect to the delegate.
   * @tparam Type Type of class or type of payload.
   * @param value_or_instance A valid reference that fits the purpose.
   */
  template<auto Candidate, typename Type>
  void connect(Type &value_or_instance) noexcept {
    instance = &value_or_instance;

    if constexpr (std::is_invocable_r_v<Ret, decltype(Candidate), Type &, Args...>) {
      fn = [](const void *payload, Args... args) -> Ret {
        Type *curr = static_cast<Type *>(const_cast<constness_as_t<void, Type> *>(payload));
        return Ret(std::invoke(Candidate, *curr, std::forward<Args>(args)...));
      };
    } else {
      fn = wrap<Candidate>(value_or_instance,
                           internal::index_sequence_for(internal::function_pointer_t<decltype(Candidate), Type>{}));
    }
  }

  /**
   * @brief Connects a free function with payload or a bound member to a
   * delegate.
   *
   * @sa connect(Type &)
   *
   * @tparam Candidate Function or member to connect to the delegate.
   * @tparam Type Type of class or type of payload.
   * @param value_or_instance A valid pointer that fits the purpose.
   */
  template<auto Candidate, typename Type>
  void connect(Type *value_or_instance) noexcept {
    instance = value_or_instance;

    if constexpr (std::is_invocable_r_v<Ret, decltype(Candidate), Type *, Args...>) {
      fn = [](const void *payload, Args... args) -> Ret {
        Type *curr = static_cast<Type *>(const_cast<constness_as_t<void, Type> *>(payload));
        return Ret(std::invoke(Candidate, curr, std::forward<Args>(args)...));
      };
    } else {
      fn = wrap<Candidate>(value_or_instance,
                           internal::index_sequence_for(internal::function_pointer_t<decltype(Candidate), Type>{}));
    }
  }

  /**
   * @brief Connects an user defined function with optional payload to a
   * delegate.
   *
   * The delegate isn't responsible for the connected object or the payload.
   * Users must always guarantee that the lifetime of an instance overcomes
   * the one of the delegate.<br/>
   * The payload is returned as the first argument to the target function in
   * all cases.
   *
   * @param function Function to connect to the delegate.
   * @param payload User defined arbitrary data.
   */
  void connect(function_type *function, const void *payload = nullptr) noexcept {
    RD_ASSERT(function != nullptr, "Uninitialized function pointer");
    instance = payload;
    fn = function;
  }

  /**
   * @brief Resets a delegate.
   *
   * After a reset, a delegate cannot be invoked anymore.
   */
  void reset() noexcept {
    instance = nullptr;
    fn = nullptr;
  }

  /**
   * @brief Returns a pointer to the stored callable function target, if any.
   * @return An opaque pointer to the stored callable function target.
   */
  [[nodiscard]] function_type *target() const noexcept {
    return fn;
  }

  /**
   * @brief Returns the instance or the payload linked to a delegate, if any.
   * @return An opaque pointer to the underlying data.
   */
  [[nodiscard]] const void *data() const noexcept {
    return instance;
  }

  /**
   * @brief Triggers a delegate.
   *
   * The delegate invokes the underlying function and returns the result.
   *
   * @warning
   * Attempting to trigger an invalid delegate results in undefined
   * behavior.
   *
   * @param args Arguments to use to invoke the underlying function.
   * @return The value returned by the underlying function.
   */
  Ret operator()(Args... args) const {
    RD_ASSERT(static_cast<bool>(*this), "Uninitialized delegate");
    return fn(instance, std::forward<Args>(args)...);
  }

  /**
   * @brief Checks whether a delegate actually stores a listener.
   * @return False if the delegate is empty, true otherwise.
   */
  [[nodiscard]] explicit operator bool() const noexcept {
    // no need to also test instance
    return !(fn == nullptr);
  }

  /**
   * @brief Compares the contents of two delegates.
   * @param other Delegate with which to compare.
   * @return False if the two contents differ, true otherwise.
   */
  [[nodiscard]] bool operator==(const delegate<Ret(Args...)> &other) const noexcept {
    return fn == other.fn && instance == other.instance;
  }

 private:
  const void *instance;
  function_type *fn;
};

/**
 * @brief Compares the contents of two delegates.
 * @tparam Ret Return type of a function type.
 * @tparam Args Types of arguments of a function type.
 * @param lhs A valid delegate object.
 * @param rhs A valid delegate object.
 * @return True if the two contents differ, false otherwise.
 */
template<typename Ret, typename... Args>
[[nodiscard]] bool operator!=(const delegate<Ret(Args...)> &lhs, const delegate<Ret(Args...)> &rhs) noexcept {
  return !(lhs == rhs);
}

/**
 * @brief Deduction guide.
 * @tparam Candidate Function or member to connect to the delegate.
 */
template<auto Candidate>
delegate(connect_arg_t<Candidate>) -> delegate<std::remove_pointer_t<internal::function_pointer_t<decltype(Candidate)>>>;

/**
 * @brief Deduction guide.
 * @tparam Candidate Function or member to connect to the delegate.
 * @tparam Type Type of class or type of payload.
 */
template<auto Candidate, typename Type>
delegate(connect_arg_t<Candidate>,
         Type &&) -> delegate<std::remove_pointer_t<internal::function_pointer_t<decltype(Candidate), Type>>>;

/**
 * @brief Deduction guide.
 * @tparam Ret Return type of a function type.
 * @tparam Args Types of arguments of a function type.
 */
template<typename Ret, typename... Args>
delegate(Ret (*)(const void *, Args...), const void * = nullptr) -> delegate<Ret(Args...)>;

} // namespace rendu

#endif //RENDU_CORE_SIGNAL_DELEGATE_H_
